package com.github.cyf.requestLock.annotation;

import com.github.cyf.common.exception.BizException;
import com.github.cyf.common.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * @author chenyifan
 * @create 2025-01-20 11:23
 */
@Aspect
@Component
@Slf4j
public class RedisRequestLockAspect {

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RedissonClient redissonClient;

    @Pointcut("@annotation(com.github.cyf.requestLock.annotation.RequestLock)")
    private void pointCut() {
    }

    @Around("pointCut()")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        if (!StringUtils.hasText(requestLock.prefix())) {
            throw new BizException("重复提交前缀不能为空");
        }
        //获取自定义key
        String lockKey = getLockKey(joinPoint);
        lockByRedisson(lockKey, requestLock);
        try {
            return joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public void lockByCache(String lockKey, RequestLock requestLock) {
        boolean success = redisUtil.setNx(lockKey, new byte[0], requestLock.expire(), requestLock.timeUnit());
        if (!success) {
            throw new BizException("您的操作太快了,请稍后重试");
        }
    }

    public void lockByRedisson(String lockKey, RequestLock requestLock) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 加锁并设置过期时间
            boolean isLock = lock.tryLock(0, requestLock.expire(), requestLock.timeUnit());
            if (!isLock) {
                throw new BizException("您的操作太快了,请稍后重试");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 生成请求锁 key
     * @param joinPoint
     * @return
     */
    public String getLockKey(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        Parameter[] parameters = method.getParameters();
        Object[] args = joinPoint.getArgs();
        StringBuilder key = new StringBuilder();
        // 如果有 @RequestKeyParam 注解，那么就只将有该注解的参数拼接起来
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].getAnnotation(RequestKeyParam.class) != null) {
                key.append(args[i]).append(requestLock.delimiter());
            } else {
                final Object arg = args[i];
                // 查看对象中的属性是否有 @RequestKeyParam 注解
                ReflectionUtils.doWithFields(args[i].getClass(), field -> {
                    if (field.getAnnotation(RequestKeyParam.class) != null) {
                        ReflectionUtils.makeAccessible(field);
                        Object fieldValue = ReflectionUtils.getField(field, arg);
                        key.append(fieldValue).append(requestLock.delimiter());
                    }
                });
            }
        }
        // 如果没有 @RequestKeyParam 注解，那么就将所有参数拼接起来
        if (key.length() <= 0) {
            for (int i = 0; i < parameters.length; i++) {
                key.append(args[i]).append(requestLock.delimiter());
            }
        }
        String lockKey = requestLock.prefix() + ":" + key.substring(0, key.length() - 1);
        log.info("lockKey: {}", lockKey);
        return lockKey;
    }

}